/*
 * Copyright (C) 2016 david.wei (lighters)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wgts.wgts.netmodle;

import android.content.Intent;
import android.text.TextUtils;


import com.wgts.wgts.AppConstant;
import com.wgts.wgts.MoHuApplication;
import com.wgts.wgts.mvp.view.activity.login.LoginActivity;
import com.wgts.wgts.netmodle.req.RefreshTokenRequest;
import com.wgts.wgts.netmodle.resp.RefreshTokenEntity;
import com.wgts.wgts.tools.AppInfor;
import com.wgts.wgts.tools.MoHuGlide;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;

import retrofit2.http.Header;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Func1;

import static com.wgts.wgts.netmodle.NetService.bodyMap;

/**
 * Email: huangdiv5@gmail.com
 * GitHub: https://github.com/alighters
 */
public class ProxyHandler implements InvocationHandler {

    private final static String TAG = "Token_Proxy";

    private final static String TOKEN = "token";

    private final static int REFRESH_TOKEN_VALID_TIME = 30;
    private static long tokenChangedTime = 0;
    private Throwable mRefreshTokenError = null;
    private boolean mIsTokenNeedRefresh;

    private Object mProxyObject;

    private IGlobalManager manager;



    public ProxyHandler(Object proxyObject, IGlobalManager manager) {
        mProxyObject = proxyObject;
        this.manager = manager;
    }

    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        return Observable.just(null).flatMap(new Func1<Object, Observable<?>>() {
            @Override
            public Observable<?> call(Object o) {
                try {
                    try {
                        if (mIsTokenNeedRefresh) {
                            updateMethodToken(method, args);
                        }
                        return (Observable<?>) method.invoke(mProxyObject, args);
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }).retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
            @Override
            public Observable<?> call(Observable<? extends Throwable> observable) {
                return observable.flatMap(new Func1<Throwable, Observable<?>>() {
                    @Override
                    public Observable<?> call(Throwable throwable) {

                        if (throwable instanceof TokenException) {
                            return refreshTokenWhenTokenInvalid();
                        } else if (throwable instanceof RefreshTokenException) {
                            // Token 不存在，执行退出登录的操作。（为了防止多个请求，都出现 Token 不存在的问题，
                            // 这里需要取消当前所有的网络请求）
                            //TODO
                            manager.exitLogin();

                            return Observable.error(throwable);
                        }
                        return Observable.error(throwable);
                    }
                });
            }
        });
    }

    /**
     * Refresh the token when the current token is invalid.
     *
     * @return Observable
     */
    private Observable<?> refreshTokenWhenTokenInvalid() {

        synchronized (ProxyHandler.class) {
            // Have refreshed the token successfully in the valid time.
            if (System.currentTimeMillis() - tokenChangedTime < REFRESH_TOKEN_VALID_TIME) {
                mIsTokenNeedRefresh = true;
                return Observable.just(true);
            } else {


//                if(AppInfor.getInstance().getRefreshToken().equals("")){
//                    manager.exitLogin();
//                }

                RefreshTokenRequest refreshTokenRequest = new RefreshTokenRequest();
                refreshTokenRequest.refreshToken = AppInfor.getInstance().getRefreshToken();

                //NetWorkFactory.getInstance(AppOkHttpClient.getInstance()).get(NetAPI.class).refreshToken(AppInfor.getInstance().getRefreshToken(), bodyMap(), AppInfor.getInstance().getAccessToken(), AppInfor.PLAT_VERSION)

                //NetWorkFactory.getInstance(AppOkHttpClient.getInstance()).createService(NetAPI.class).refreshToken(AppInfor.getInstance().getRefreshToken(), bodyMap(), AppInfor.getInstance().getAccessToken(), AppInfor.PLAT_VERSION);

                NetWorkFactory.getInstance(AppOkHttpClient.getInstance()).get(NetAPI.class).refreshToken(AppInfor.getInstance().getRefreshToken(), bodyMap(), AppInfor.getInstance().getAccessToken(), AppInfor.PLAT_VERSION)
                        .subscribe(new Subscriber<HttpResult<RefreshTokenEntity>>() {
                            @Override
                            public void onCompleted() {

                            }

                            @Override
                            public void onError(Throwable e) {
                                mRefreshTokenError = e;
                                manager.exitLogin();
                            }

                            @Override
                            public void onNext(HttpResult<RefreshTokenEntity> refreshTokenEntityHttpResult) {
                                mIsTokenNeedRefresh = true;
                                AppInfor.getInstance().setAccessToken(refreshTokenEntityHttpResult.data.accessToken);
                                AppInfor.getInstance().setRefreshToken(refreshTokenEntityHttpResult.data.refreshToken);
                                tokenChangedTime = System.currentTimeMillis();
                            }
                        });

                if (mRefreshTokenError != null ) {
                    return Observable.error(mRefreshTokenError);
                } else {
                    return Observable.just(true);
                }
            }
        }
    }

    /**
     * Update the token of the args in the method.
     *
     * PS： 因为这里使用的是 GET 请求，所以这里就需要对 Query 的参数名称为 token 的方法。
     * 若是 POST 请求，或者使用 Body ，自行替换。因为 参数数组已经知道，进行遍历找到相应的值，进行替换即可（更新为新的 token 值）。
     */
    private void updateMethodToken(Method method, Object[] args) {
        if (mIsTokenNeedRefresh && !TextUtils.isEmpty(NetWorkConstant.PUBLIC_ACCESSTOKEN)) {
            Annotation[][] annotationsArray = method.getParameterAnnotations();
            Annotation[] annotations;
            if (annotationsArray != null && annotationsArray.length > 0) {
                for (int i = 0; i < annotationsArray.length; i++) {
                    annotations = annotationsArray[i];
                    for (Annotation annotation : annotations) {
                        if (annotation instanceof Header) {
                            if (NetWorkConstant.PUBLIC_ACCESSTOKEN.equals(((Header) annotation).value())) {
                                args[i] = AppInfor.getInstance().getAccessToken();
                            }
                        }
                    }
                }

            }
            mIsTokenNeedRefresh = false;
        }
    }
}
